﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI.UI;
using Microsoft.UI.Xaml.Data;
using Newtonsoft.Json.Linq;
using TextControlBoxNS;

namespace MetaDataParser.ViewModels;

public partial class MainViewModel : ObservableRecipient
{
    public MainViewModel()
    {
        adBindingInfo = new AdvancedCollectionView(new ObservableCollection<BindingInfoViewModel>());
        adBindingInfo.Filter = (it) =>
        {
            if (string.IsNullOrEmpty(SearchString))
            {
                return true;
            }

            if (it is BindingInfoViewModel bindingInfoViewModel)
            {
                return bindingInfoViewModel.Guid.Contains(SearchString)
                       || bindingInfoViewModel.JsCode.Contains(searchString)
                       || bindingInfoViewModel.VarName.Contains(SearchString);
            }

            return false;
        };
    }

    [ObservableProperty] private string? metadataJson;

    [ObservableProperty] private bool parsed;

    [ObservableProperty] private CodeLanguage language = TextControlBox.GetCodeLanguageFromId(CodeLanguageId.Json);

    [ObservableProperty] private AdvancedCollectionView adBindingInfo;

    [ObservableProperty] private string searchString;

    private string _pageName;

    [RelayCommand]
    private void Clear()
    {
        SearchString = "";
        Parsed = false;
        MetadataJson = null;
        AdBindingInfo.Clear();
    }

    [RelayCommand]
    private void Parse()
    {
        Parsed = true;
        var payload = JObject.Parse(MetadataJson);
        _pageName = payload.Properties().First().Name;
        var metaData = JObject.Parse(payload[_pageName]?["metaData"].Value<string>());
        ParseCellBindingInfo(metaData);
        ParseValueColumnBindingInfo(metaData);
        ParseDisplayColumnBindingInfo(metaData);
        ParseItemsQueryBindingInfo(metaData);
        ParseUploadLimit(metaData);
        ParseCacheSetting(metaData);
        ParseDropDownSubItems(metaData);
        ParseCommandList(metaData);
        ParseListview(metaData);
        ParseFormulaInfos(metaData);
    }

    private void ParseListview(JObject metaData)
    {
        var listviews = metaData["ListViews"] as JArray;
        if (listviews == null)
        {
            return;
        }
        foreach (var listview in listviews)
        {
            var listViewName = listview["Data"]?["Name"].Value<string>();
            var guidTokens = (listview.SelectToken("Data.Columns") as JContainer).Descendants()
                .Where(t => t.Type == JTokenType.Property && ((JProperty)t).Name == "GUID")
                .OfType<JProperty>()
                .Select(it => it.Value)
                .ToList();

            var listViewPath = listview.Path;

            foreach (var guidToken in guidTokens)
            {
                var path = guidToken.Path[listViewPath.Length..];
                var varName = _pageName +listViewName + "_" + CalculateMD5Hash(path);
                AdBindingInfo.Add(new BindingInfoViewModel()
                {
                    Guid = $@"""{guidToken?.Value<string>()}""",
                    VarName = varName,
                    JsCode =
                        $"const {varName} = {_pageName}MetaData.ListViews.single(it => it?.Data?.Name === '{listViewName}'){path};"
                });
            }
        }
    }

    private void ParseCommandList(JObject metaData)
    {
        var commandsCells = metaData.SelectTokens("Cells[?(@.CellType.CommandList)]").ToList();
        foreach (var commandsCell in commandsCells)
        {
            var row = commandsCell["Row"].Value<int>();
            var colum = commandsCell["Column"].Value<int>();

            var guidTokens = (commandsCell as JContainer).Descendants()
                .Where(t => t.Type == JTokenType.Property && (((JProperty)t).Name == "GUID" || ((JProperty)t).Name == "ID") )
                .OfType<JProperty>()
                .Select(it => it.Value)
                .ToList();

            var cellPath = commandsCell.Path;

            foreach (var guidToken in guidTokens)
            {
                var path = guidToken.Path[cellPath.Length..];
                var varName = _pageName + "_" + row + "_" + colum + CalculateMD5Hash(path);
                AdBindingInfo.Add(new BindingInfoViewModel()
                {
                    Guid = $@"""{guidToken?.Value<string>()}""",
                    VarName = varName,
                    JsCode =
                        $"const {varName} = {_pageName}MetaData.Cells.single(it => it.Row === {row} && it.Column === {colum}){path};"
                });
            }
        }
    }

    private void ParseFormulaInfos(JObject metaData)
    {
        var formulaInfos = metaData["FormulaInfos"]?.Values().OfType<JArray>().SelectMany(it=> it);
        if (formulaInfos == null)
        {
            return;
        }
        foreach (var formulaInfo in formulaInfos)
        {
            var row = formulaInfo["RowIndex"].Value<int>();
            var colum = formulaInfo["ColumnIndex"].Value<int>();
            var formula = formulaInfo.Value<string>("Formula");
            if (formula?.Contains("CLIENTODATA") != true)
            {
                continue;
            }

            var match = _odataRegex.Match(formula);
            var path = formulaInfo["Formula"].Path;
            if (!match.Success)
            {
                continue;
            }

            var GUID = match.Groups[1].Value;
            var varName = _pageName + "_" + row + "_" + colum + "OData";
            AdBindingInfo.Add(new BindingInfoViewModel()
            {
                Guid = $@"""{GUID}""",
                VarName = varName,
                JsCode =
                    $"const {varName} = oDataRegex.exec({_pageName}MetaData.{path})[1];"
            });
        }
    }

    private void ParseDropDownSubItems(JObject metaData)
    {
        var dropDownSubItems = metaData.SelectTokens("Cells[?(@.CellType.DropDownSubItems && @.BindingInfo)]").ToList();
        foreach (var dropDownSubItem in dropDownSubItems)
        {
            var bindingInfo = dropDownSubItem["BindingInfo"];
            var tableName = bindingInfo?["TableName"]?.ToString();
            var columnName = bindingInfo?["ColumnName"]?.ToString();
            var varName = $"{tableName}{columnName}DropDownSubItems";
            AdBindingInfo.Add(new BindingInfoViewModel()
            {
                Guid = $@"""{bindingInfo?["GUID"]}""",
                VarName = varName,
                JsCode =
                    $@"const {varName} = {_pageName}MetaData.Cells.single(it => it.BindingInfo?.TableName === '{tableName}' && it.BindingInfo?.ColumnName === '{columnName}' && it.DropDownSubItems).DropDownSubItems.map(it=> {{ it.BindingInfo = it.BindingInfo.GUID; return it; }});"
            });
        }
    }

    private void ParseCacheSetting(JObject metaData)
    {
        var CacheSettingCells = metaData.SelectTokens("Cells[?(@.ServerPropertiesId.CacheSetting && @.BindingInfo)]")
            .ToList();
        foreach (var cacheSettingCell in CacheSettingCells)
        {
            var bindingInfo = cacheSettingCell["BindingInfo"];
            var tableName = bindingInfo?["TableName"]?.ToString();
            var columnName = bindingInfo?["ColumnName"]?.ToString();
            var varName = $"{tableName}{columnName}CacheSetting";
            AdBindingInfo.Add(new BindingInfoViewModel()
            {
                Guid = $@"""{cacheSettingCell["ServerPropertiesId"]?["CacheSetting"]}""",
                VarName = varName,
                JsCode =
                    $@"const {varName} = {_pageName}MetaData.Cells.single(it => it.BindingInfo?.TableName === '{tableName}' && it.BindingInfo?.ColumnName === '{columnName}' && it?.ServerPropertiesId?.CacheSetting).ServerPropertiesId.CacheSetting;"
            });
        }
    }

    private void ParseUploadLimit(JObject metaData)
    {
        var uploadLimitCells = metaData.SelectTokens("Cells[?(@.ServerPropertiesId.UploadLimit && @.BindingInfo)]")
            .ToList();
        foreach (var uploadLimitCell in uploadLimitCells)
        {
            var bindingInfo = uploadLimitCell["BindingInfo"];
            var tableName = bindingInfo?["TableName"]?.ToString();
            var columnName = bindingInfo?["ColumnName"]?.ToString();
            var varName = $"{tableName}{columnName}UploadLimit";
            AdBindingInfo.Add(new BindingInfoViewModel()
            {
                Guid = $@"""{uploadLimitCell["ServerPropertiesId"]?["UploadLimit"]}""",
                VarName = varName,
                JsCode =
                    $@"const {varName} = {_pageName}MetaData.Cells.single(it => it.BindingInfo?.TableName === '{tableName}' && it.BindingInfo?.ColumnName === '{columnName}' && it?.ServerPropertiesId?.UploadLimit).ServerPropertiesId.UploadLimit;"
            });
        }
    }

    private void ParseItemsQueryBindingInfo(JObject metaData)
    {
        var itemsQueryBindingInfoCells = metaData
            .SelectTokens("Cells[?(@.CellType.ItemsQuery.ColumnBindingInfo && @.BindingInfo)]").ToList();
        foreach (var itemsQueryBindingInfo in itemsQueryBindingInfoCells)
        {
            var bindingInfo = itemsQueryBindingInfo["BindingInfo"];
            var tableName = bindingInfo?["TableName"]?.ToString();
            var columnName = bindingInfo?["ColumnName"]?.ToString();
            var varName = $"{tableName}{columnName}ItemsQueryColumnBindingInfo";
            AdBindingInfo.Add(new BindingInfoViewModel()
            {
                Guid = $@"""{itemsQueryBindingInfo["CellType"]?["ItemsQuery"]?["ColumnBindingInfo"]?["GUID"]}""",
                VarName = varName,
                JsCode =
                    $@"const {varName} = {_pageName}MetaData.Cells.single(it => it.BindingInfo?.TableName === '{tableName}' && it.BindingInfo?.ColumnName === '{columnName}' && it?.CellType?.ItemsQuery?.ColumnBindingInfo).CellType.ItemsQuery.ColumnBindingInfo.GUID;"
            });
        }
    }

    private void ParseDisplayColumnBindingInfo(JObject metaData)
    {
        var displayColumnBindingInfoCells =
            metaData.SelectTokens("Cells[?(@.CellType.DisplayColumnBindingInfo)]").ToList();
        foreach (var displayColumnBindingInfoCell in displayColumnBindingInfoCells)
        {
            var bindingInfoViewModel = GetBindingInfoViewModelFromCell(displayColumnBindingInfoCell,
                cell => cell["CellType"]?["DisplayColumnBindingInfo"], "DisplayColumnBindingInfo",
                (tableName, columnName) =>
                    $@"Cells.single(it => it.CellType?.DisplayColumnBindingInfo?.TableName === '{tableName}' && it.CellType?.DisplayColumnBindingInfo?.ColumnName === '{columnName}').CellType.DisplayColumnBindingInfo.GUID;");
            AdBindingInfo.Add(bindingInfoViewModel);
        }
    }

    private void ParseValueColumnBindingInfo(JObject metaData)
    {
        var valueColumnBindingInfoCells = metaData.SelectTokens("Cells[?(@.CellType.ValueColumnBindingInfo)]").ToList();
        foreach (var valueColumnBindingInfoCell in valueColumnBindingInfoCells)
        {
            var bindingInfoViewModel = GetBindingInfoViewModelFromCell(valueColumnBindingInfoCell,
                cell => cell["CellType"]?["ValueColumnBindingInfo"], "ValueColumnBindingInfo",
                (tableName, columnName) =>
                    $@"Cells.single(it => it.CellType?.ValueColumnBindingInfo?.TableName === '{tableName}' && it.CellType?.ValueColumnBindingInfo?.ColumnName === '{columnName}').CellType.ValueColumnBindingInfo.GUID;");
            AdBindingInfo.Add(bindingInfoViewModel);
        }
    }

    private void ParseCellBindingInfo(JObject metaData)
    {
        var bindingInfoCells = metaData.SelectTokens("Cells[?(@.BindingInfo)]").ToList();
        foreach (var bindingInfoCell in bindingInfoCells)
        {
            var bindingInfoViewModel = GetBindingInfoViewModelFromCell(bindingInfoCell, cell => cell["BindingInfo"],
                "BindingInfo",
                (tableName, columnName) =>
                    $@"Cells.single(it => it.BindingInfo?.TableName === ""{tableName}"" && it.BindingInfo?.ColumnName === ""{columnName}"").BindingInfo.GUID;");
            AdBindingInfo.Add(bindingInfoViewModel);
        }
    }

    private static Regex _odataRegex = OdataRegex();

    string CalculateMD5Hash(string input)
    {
        var md5 = MD5.Create();

        var inputBytes = Encoding.ASCII.GetBytes(input);

        var hash = md5.ComputeHash(inputBytes);

        var sb = new StringBuilder();

        foreach (var t in hash)
        {
            sb.Append(t.ToString("X2"));
        }

        return sb.ToString();
    }

    BindingInfoViewModel GetBindingInfoViewModelFromCell(JToken infoCell,
        Func<JToken, JToken> getBindingInfo, string varExtension, Func<string, string, string> generateJsCode)
    {
        var bindingInfo = getBindingInfo(infoCell);
        var tableName = bindingInfo?["TableName"]?.ToString();
        var columnName = bindingInfo?["ColumnName"]?.ToString();
        var varName = $"{_pageName}{tableName}{columnName}{varExtension}";
        return new BindingInfoViewModel()
        {
            Guid = $@"""{bindingInfo?["GUID"]}""",
            VarName = varName,
            JsCode = $"const {varName} = {_pageName}MetaData.{generateJsCode(tableName, columnName)}"
        };
    }

    [GeneratedRegex("CLIENTODATA\\s*\\(\\s*\"(.*?)\"\\s*,.*?\\)")]
    private static partial Regex OdataRegex();
}

public partial class BindingInfoViewModel : ObservableObject
{
    [ObservableProperty] private string? guid;

    [ObservableProperty] private string? varName;

    [ObservableProperty] private string? jsCode;
}